package Farsail::ActionSet;

use strict; 
use warnings;
use YAML qw/LoadFile/;
use File::Glob qw/:glob/;
use Farsail::Action;
use Farsail::Util qw/expand_file/;
use Farsail::Config;

use Carp;
our $DEFAULT_NAMESPACE = 'global';

sub new {
    my $_class = shift;
    my $class = ref $_class || $_class;
    my $config = shift || confess "Need config!";
    my $self = {
        actions => {},
        cooked_actions => {},
        config => $config,
    };
    bless $self, $class;
    $self->load_action( expand_file($config->action_define_file, $config->rootdir) );
    return $self;
}

sub load_action {
    my ($self, $def) = @_;
    unless ( ref $def eq 'HASH' ) {
        return if exists $self->{INCLUDES}{$def};
        $self->{INCLUDES}{$def}++;
        eval {
            $def = LoadFile($def);
        };
        if ( $@ ) {
            confess "Load action '$def' failed: $@\n";
        }
    }
    if ( exists $def->{include} ) {
        my (@includes, @files);
        if ( ref $def->{include} eq 'ARRAY' ) {
            @includes = @{$def->{include}};
        } elsif ( !ref $def->{include} ) {
            push @includes, $def->{include};
        }
        for ( @includes ) {
            my $file = expand_file($_, $self->{config}->rootdir);
            if ( -e $file ) {
                push @files, $file;
            } else {
                push @files, bsd_glob($file, GLOB_ERROR);
            }
        }
        for ( @files ) {
            $self->load_action($_);
        }
        delete $def->{include};
    }
    my $actions = $self->{actions};
    for my $ns ( keys %$def ) {
        for ( keys %{$def->{$ns}} ) {
            $actions->{$ns}{$_} = $def->{$ns}{$_};
        }
    }
    return $actions;
}

sub register_action {
    my $self = shift;
    my ($name, $action) = @_;
    if ( !$name ) {
        confess "Unknown action name!\n";
    }
    my $namespace = ( exists $action->{namespace}
                          ? $action->{namespace}
                          : $DEFAULT_NAMESPACE );
    $self->{actions}{$namespace}{$name} = $action;
}

sub _parse_action_name {
    my ($action, $default_ns) = @_;
    my ($ns, $name) = split /\./, $action, 2;
    if ( !$name ) {
        $name = $ns;
        $ns = $default_ns || $DEFAULT_NAMESPACE;
    }
    return ($ns, $name);
}

sub has_action {
    my $self = shift;
    my ($action, $default_ns) = @_;
    return if !$action;
    my ($ns, $name);
    if ( UNIVERSAL::isa($action, 'Farsail::Action') ) {
        ($ns, $name) = ($action->namespace, $action->name);
    } else {
        ($ns, $name) = _parse_action_name($action, $default_ns);
    }
    my $p = $self->{actions}->{$ns};
    if ( ref $p && exists $p->{$name} ) {
        return 1;
    }
    elsif ( exists $p->{module} ) {
        eval( "require $p->{module}");
        if ( $@ ) {
            confess "Require $p->{module} failed: $@\n";
        }
        return $p->{module}->can('ACTION_'.$name);
    }
    return 0;
}

sub action {
    my $self = shift;
    my ($action, $default_ns, $nocheck) = @_;
    return $action if UNIVERSAL::isa($action, 'Farsail::Action');
    my ($ns, $name) = _parse_action_name($action, $default_ns);
    if ( exists $self->{cooked_actions}{$ns}{$name} ) {
        return $self->{cooked_actions}{$ns}{$name};
    }
    if ( !$nocheck && !$self->has_action($name, $ns) ) {
        confess "No such action '$ns.$name'!\n";
    }
    return $self->{cooked_actions}{$ns}{$name}
        = Farsail::Action->new($self->{actions}{$ns}, $ns, $name);
}

sub requires {
    my $self = shift;
    my $action = shift;
    if ( !ref $action ) {
        $action = $self->action($action);
    }
    my $requires = shift || {};
    my $module = $action->module;
    if ( $module ) {
        $requires->{$module}++;
    }
    for ( $action->depends ) {
        $self->requires($_, $requires);
    }
    return $requires;
}

sub args {
    my $self = shift;
    my $action = shift;
    if ( !ref $action ) {
        $action = $self->action($action);
    }
    my %args = %{$action->args};
    for ( $action->depends ) {
        my $sub_args = $self->args($_);
        for ( keys %$sub_args ) {
            next if exists $args{$_};
            $args{$_} = $sub_args->{$_};
        }
    }
    return \%args;
}

sub depends {
    my $self = shift;
    my $action = shift;
    my $finished = shift || [];
    my $completed = shift || {};
    if ( !ref $action ) {
        $action = $self->action($action);
    }
    return if $completed->{$action->full_name}++;
    for ( $action->depends ) {
        $self->depends($_, $finished, $completed);
    }
    push @{$finished}, $action->full_name;
    return $finished;
}

sub use_namespace {
    my $self = shift;
    $DEFAULT_NAMESPACE = shift;
}

1;
